/*
Copyright 2008, 2009 Joonas Lehtolahti

This file is part of Shared Datatypes and Utilities.

Shared Datatypes and Utilities is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Shared Datatypes and Utilities is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Shared Datatypes and Utilities.  If not, see <http://www.gnu.org/licenses/>.
*/

/* SHA-1 implementation using special non0string data type */

#include <string.h>
#include <stdio.h>
#include "n0sha1.h"

#define WORD(x) (unsigned int)((x) & 0xFFFFFFFF)

#define UINT unsigned int

#define SCS(bits,word) ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32-(bits))))

UINT SLF(UINT t, UINT B, UINT C, UINT D) {
	if (t <= 19)
		return WORD((B&C)|((~B)&D));
	if (t <= 39)
		return WORD(B^C^D);
	if (t <= 59)
		return WORD((B&C)|(B&D)|(C&D));
	return WORD(B^C^D);
}

DLLEXPORT(non0string*) n0sha1(non0string *message) {
	UINT K[] = {
		0x5A827999,
		0x6ED9EBA1,
		0x8F1BBCDC,
		0xCA62C1D6
	};
	UINT H[] = {
		0x67452301,
		0xEFCDAB89,
		0x98BADCFE,
		0x10325476,
		0xC3D2E1F0
	};
	UINT blocks = message->l / 64;
	UINT final_block_length = message->l - 64*blocks;
	
	UINT A, B, C, D, E, temp, W[80], i, t, length;
	
	non0string *processor, *final = n0salloc(41);
	final->l = 40;
	
	/** MESSAGE PADDING PART **/
	
	if (final_block_length >= 56) {
		/* Need a completely new block to hold the length */
		blocks++;
	}
	processor = n0salloc(64*(blocks+1));
	memset(processor->s, 0, processor->l);
	memcpy(processor->s, message->s, message->l);
	processor->s[message->l] = (unsigned char)0x80;
	length = message->l*8;
	processor->s[processor->l-4] = (unsigned char)((length>>24)&0xFF);
	processor->s[processor->l-3] = (unsigned char)((length>>16)&0xFF);
	processor->s[processor->l-2] = (unsigned char)((length>>8)&0xFF);
	processor->s[processor->l-1] = (unsigned char)(length&0xFF);
	
	/** MESSAGE PROCESSING **/
	
	for (i = 0; i <= blocks; i++) {
		for (t = 0; t <= 15; t++) {
			temp = 64*i+4*t;
			W[t] = processor->s[temp++] << 24;
			W[t] |= processor->s[temp++] << 16;
			W[t] |= processor->s[temp++] << 8;
			W[t] |= processor->s[temp];
		}
		for (t = 16; t <= 79; t++) {
			W[t] = SCS(1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
		}
		A = H[0]; B = H[1]; C = H[2]; D = H[3]; E = H[4];
		for (t = 0; t <= 79; t++) {
			temp = SCS(5, A) + SLF(t, B, C, D) + E + W[t] + K[t/20];
			E = D; D = C;
			C = SCS(30, B);
			B = A; A = temp;
		}
		H[0] = H[0] + A; H[1] = H[1] + B; H[2] = H[2] + C; H[3] = H[3] + D; H[4] = H[4] + E;
	}
	
	/** RETURNING **/
	
	n0sfree(processor);
	sprintf((char *)(final->s), "%08x%08x%08x%08x%08x", H[0], H[1], H[2], H[3], H[4]);
	return final;
}
